﻿using System;
using System.Collections;
using System.Collections.Generic;

namespace NetAssist.Collections
{
  /// <summary>
  /// Represents a unique last-in, first-out (LIFO) collection of objects.
  /// </summary>
  /// <typeparam name="T">Specifies the type of elements in the stack.</typeparam>
  public class UniqueStack<T> : IEnumerable<T>, ICollection, IEnumerable
  {

    private List<T> uniqueStack;

    /// <summary>
    /// Initializes a new instance of the UniqueStack&lt;T@gt; class that is empty and has the default initial capacity.
    /// </summary>
    public UniqueStack()
    {
      uniqueStack = new List<T>();
    }

    /// <summary>
    /// Initializes a new instance of the UniqueStack&lt;T&gt; class that contains elements copied from the specified collection and has sufficient  capacity to accommodate the number of elements copied.
    /// </summary>
    /// <param name="collection">The collection whose elements are copied to the new UniqueStack&lt;T&gt;.</param>    
    /// <exception cref="System.ArgumentNullException">collection is null.</exception>
    public UniqueStack(IEnumerable<T> collection)
    {
      uniqueStack = new List<T>(collection);
    }

    /// <summary>
    /// Initializes a new instance of the UniqueStack&lt;T&gt; class that is empty and has the specified initial capacity.
    /// </summary>
    /// <param name="capacity">The initial number of elements that the UniqueStack&lt;T&gt; can contain.</param>
    /// <exception cref="System.ArgumentOutOfRangeException">capacity is less than zero.</exception>
    public UniqueStack(int capacity)
    {
      uniqueStack = new List<T>(capacity);
    }

    /// <summary>
    /// Gets the number of elements contained in theon UniqueStack&lt;T&gt;.
    /// </summary>
    /// <returns>The number of elements contained in the UniqueStack&lt;T&gt;.</returns>
    public int Count { get { return uniqueStack.Count; } }

    /// <summary>
    /// Removes all objects from the UniqueStack&lt;T&gt;.
    /// </summary>
    public void Clear()
    {
      uniqueStack.Clear();
    }

    /// <summary>
    /// Determines whether an element is in the UniqueStack&lt;T&gt;.
    /// </summary>
    /// <param name="item">The object to locate in the UniqueStack&lt;T&gt;. The value can be null for reference types.</param>
    /// <returns>true if item is found in the UniqueStack&lt;T&gt;; otherwise, false.</returns>
    public bool Contains(T item)
    { return uniqueStack.Contains(item); }


    /// <summary>
    /// Copies the UniqueStack&lt;T&gt; elements to an existing one-dimensional System.Array, starting at the specified array index.
    /// </summary>
    /// <param name="array">The one-dimensional System.Array that is the destination of the elements copied from UniqueStack&lt;T&gt;. The System.Array must have zero-based indexing.</param>
    /// <param name="arrayIndex">The zero-based index in array at which copying begins.</param>
    /// <exception cref="System.ArgumentNullException">array is null.</exception>
    /// <exception cref="System.ArgumentOutOfRangeException">arrayIndex is less than zero.</exception>
    /// <exception cref="System.ArgumentException">The number of elements in the source UniqueStack&lt;T&gt; is greater than the available space from arrayIndex to the end of the destination array.</exception>
    public void CopyTo(T[] array, int arrayIndex)
    {
      uniqueStack.CopyTo(array, arrayIndex);
    }

    /// <summary>
    /// Removes and returns the object at the top of the UniqueStack&lt;T&gt;.
    /// </summary>
    /// <returns>The object that is removed from the top of the UniqueStack&lt;T&gt;.</returns>
    /// <exception cref="System.InvalidOperationException">The UniqueStack&lt;T&gt; is empty.</exception>
    public T Pop()    {

      int lastIdx = uniqueStack.Count - 1;
      T temp = uniqueStack[lastIdx];
      uniqueStack.RemoveAt(lastIdx);
      return temp;
    }

    /// <summary>
    /// Adds an object to the top of the UniqueStack&lt;T&gt;. if an instance on the object exists elsewhere in the list, it will be removed.
    /// </summary>
    /// <param name="item">The object to add to the UniqueStack&lt;T&gt;. The value can be null for reference types.</param>
    /// <remarks>If the object already exists in the stack, it is removed and than added to the top of the UniqueStack&lt;T&gt;.</remarks>
    public void Push(T item)
    {
      if (uniqueStack.Contains(item))
        uniqueStack.Remove(item);

      uniqueStack.Add(item);
    }

    /// <summary>
    /// Returns the object at the top of the UniqueStack&lt;T&gt; without removing it.
    /// </summary>
    /// <returns>The object at the top of the UniqueStack&lt;T&gt;.</returns>
    /// <exception cref="System.InvalidOperationException">The UniqueStack&lt;T&gt; is empty.</exception>
    public T Peek()
    {
      int lastIdx = uniqueStack.Count - 1;
      return uniqueStack[lastIdx];
    }
    /// <summary>
    /// Copies the UniqueStack&lt;T&gt; elements to a new array.
    /// </summary>
    /// <returns>A new array containing elements copied from the UniqueStack&lt;T&gt;.</returns>
    public T[] ToArray()
    {
      return uniqueStack.ToArray();
    }

    /// <summary>
    /// Sets the capacity to the actual number of elements in the UniqueStack&lt;T&gt;, if that number is less than 90 percent of current capacity.    
    /// </summary>
    public void TrimExcess()
    {
      uniqueStack.TrimExcess();
    }

    /// <summary>
    /// Returns an enumerator that iterates through a collection.
    /// </summary>
    /// <returns>An System.Collections.IEnumerator object that can be used to iterate through the collection.</returns>
    public IEnumerator<T> GetEnumerator()
    {
      return uniqueStack.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
      return this.GetEnumerator();
    }

    /// <summary>
    /// Copies the elements of the System.Collections.ICollection to an System.Array, starting at a particular System.Array index.
    /// </summary>
    /// <param name="array">The one-dimensional System.Array that is the destination of the elements copied from System.Collections.ICollection. The System.Array must have zero-based indexing.</param>
    /// <param name="index">The zero-based index in array at which copying begins.</param>
    /// <exception cref="System.ArgumentNullException">array is null.</exception>
    /// <exception cref="System.ArgumentOutOfRangeException">index is less than zero.</exception>
    /// <exception cref="System.ArgumentException">array is multidimensional -or- The number of elements in the source System.Collections.ICollection is greater than the available space from index to the end of the destination array.</exception>   
    /// <exception cref="System.ArgumentException">The type of the source System.Collections.ICollection cannot be cast automatically to the type of the destination array.</exception>    
    public void CopyTo(Array array, int index)
    {
      Helper.InvalidateNullParam(array, "array");
      foreach (T item in this.ToArray())
      {
        array.SetValue(item, index);
        index++;
      }
    }

    /// <summary>
    /// Gets a value indicating whether access to the System.Collections.ICollection is synchronized (thread safe).
    /// </summary>
    /// <returns>true if access to the System.Collections.ICollection is synchronized (thread safe); otherwise, false.</returns>
    public bool IsSynchronized
    {
      get { return false; }
    }

    /// <summary>
    /// Gets an object that can be used to synchronize access to the System.Collections.ICollection.
    /// </summary>
    /// <returns>An object that can be used to synchronize access to the System.Collections.ICollection.</returns>
    public object SyncRoot
    {
      get { return this; }
    }

    /// <summary>
    /// Converts a UniqueStack&lt;T&gt; to a Stack&lt;T&gt;.
    /// </summary>
    /// <param name="stack">The UniqueStack&lt;T&gt; to convert.</param>
    /// <returns>The UniqueStack&lt;T&gt; as a Stack&lt;T&gt;</returns>
    public static implicit operator Stack<T>(UniqueStack<T> stack)
    {
      Helper.InvalidateNullParam(stack, "stack");
      return new Stack<T>(stack.uniqueStack);
    }
  }
}
